<?php
require_once (TIN_APP_PATH . 'include/TinPHP/TinMemcache.php');

/**
 * 获取geo范围
 */
class GeoCodeUtil {
	public static $tableName = "Geo";
	
	public static $dMask = array (
			'village' => 255,
			'town' => 16128,
			'county' => 1032192,
			'city' => 66060288,
			'province' => 4227858432 
	);
	public static $dParentMask = array (
			'village' => 4294967295,
			'town' => 4294967040,
			'county' => 4294950912,
			'city' => 4293918720,
			'province' => 4227858432 
	);
	public static $dOffside = array (
			'5' => 1,
			'4' => 256,
			'3' => 16384,
			'2' => 1048576,
			'1' => 67108864 
	);
	public static $geoLevelMap = array (
			'5' => '/vill',
			'4' => '/town',
			'3' => '/county',
			'2' => '/city',
			'1' => '/province',
			'0' => '/country' 
	);
	public static $segment = array (
			'1' => 4227858432,
			'2' => 4293918720,
			'3' => 4294950912,
			'4' => 4294967040,
			'5' => 4294967295 
	);
	/** 根据code获取等级名称 */
	public static function getGeoLevel($dGeoCode) {
		if (0 == $dGeoCode) {
			return 'country';
		}		
		$dRes = $dGeoCode & GeoCodeUtil::$dMask ['village'];
		if ($dRes != 0) {
			return 'village';
		}
		$dRes = $dGeoCode & GeoCodeUtil::$dMask ['town'];
		if ($dRes != 0) {
			return 'town';
		}
		$dRes = $dGeoCode & GeoCodeUtil::$dMask ['county'];
		if ($dRes != 0) {
			return 'county';
		}
		
		$dRes = $dGeoCode & GeoCodeUtil::$dMask ['city'];
		if ($dRes != 0) {
			return 'city';
		}
		return 'province';
	}
	/** 根据code获取等级数值 */
	public static function getGeoIntLevel($geoCode) {
		$levelType = self::getGeoLevel ( $geoCode );
		switch ($levelType) {
			case "province" :
				return 1;
			case "city" :
				return 2;
			case "county" :
				return 3;
			case "town" :
				return 4;
			case "village" :
				return 5;
		}
		return 0;
	}
	/** 根据code获取下级范围 */
	public static function getGeoCodeRange($dGeoCode) {
		if (is_null ( $dGeoCode )) {
			return;
		}
		if ($dGeoCode == 0) {
			$dGeoCodeRange ['first'] = 1;
			$dGeoCodeRange ['last'] = self::$dMask ['province'];
			return $dGeoCodeRange;
		}
		
		$type = GeoCodeUtil::getGeoLevel ( $dGeoCode );
		if ($type == 'village') {
			$dGeoCodeRange ['first'] = $dGeoCode;
			$dGeoCodeRange ['last'] = $dGeoCode;
		} else {
			$dGeoCodeRange ['first'] = $dGeoCode + 1;
			if ($type == 'town') {
				$dGeoCodeRange ['last'] = $dGeoCode + GeoCodeUtil::$dMask ['village'];
			} else {
				if ($type == 'county') {
					$dGeoCodeRange ['last'] = $dGeoCode + GeoCodeUtil::$dMask ['town'] + GeoCodeUtil::$dMask ['village'];
				} else {
					if ($type == 'city') {
						$dGeoCodeRange ['last'] = $dGeoCode + GeoCodeUtil::$dMask ['county'] + GeoCodeUtil::$dMask ['town'] + GeoCodeUtil::$dMask ['village'];
					} else {
						$dGeoCodeRange ['last'] = $dGeoCode + GeoCodeUtil::$dMask ['city'] + GeoCodeUtil::$dMask ['county'] + GeoCodeUtil::$dMask ['town'] + GeoCodeUtil::$dMask ['village'];
					}
				}
			}
		}
		return $dGeoCodeRange;
	}
	
	public static function getGeoCodeArray($geoCode) {
		$codeList = array ();
		$level = ( int ) GeoCodeUtil::getGeoIntLevel($geoCode); //GeoCodeUtil::getCategoryLevel ( $geoCode );
		for($i = 1; $i < $level; $i ++) {
			if (1 === $i) {
				$c = self::shuffleBits ( $geoCode, 26 );
				$codeList [] = $c;
			} else if (2 === $i) {
				$c = self::shuffleBits ( $geoCode, 20 );
				$codeList [] = $c;
			} else if (3 === $i) {
				$c = self::shuffleBits ( $geoCode, 14 );
				$codeList [] = $c;
			} else if (4 === $i) {
				$c = self::shuffleBits ( $geoCode, 8 );
				$codeList [] = $c;
			}
		}
		
		$codeList [] = $geoCode;
		return $codeList;
	}
	
	/* $v & ((2^32 - 1) - (2^bits - 1)) */
	private static function shuffleBits($v, $bits) {
		$sgnMax = ( float ) 2147483648; // 2^31	
		if ($v > $sgnMax) {
			$tmp = $v - $sgnMax;
			$tmp >>= $bits;
			$tmp <<= $bits;
			$v = $tmp + $sgnMax;
		} else {
			$v >>= $bits;
			$v <<= $bits;
		}	
		return $v;
	}

	public static function makeSqlScriptFromXML($xmlFile, $outHandle, $dGeoCode, $level) {
		$ynode = new YoyoDataNode ();
		YoyoXmlUtil::loadFromXmlFile ( $ynode, $xmlFile );
		$subList = $ynode->getChildren ();
		foreach ( $subList as $subNode ) {
			GeoCodeUtil::calSqlScript ( $subNode, $dGeoCode, $level, $outHandle );
		}
	}
	public static function calGeoCode(&$ynode, $dGeoCode, $level) {
		$keystr = GeoCodeUtil::$geoLevelMap [$level];
		$yKey = YoyoKey::getKey ( $keystr );
		$nodeList = array ();
		$ynode->getNodeList ( $nodeList, $yKey );
		foreach ( $nodeList as $node ) {
			$name = $node->getAttribute ( "name" );
			$dGeoCode = $dGeoCode + GeoCodeUtil::$dOffside [$level];
			// $geoCode = decbin($dGeoCode);
			$geoCode = $dGeoCode;
			$node->setAttribute ( "code", $geoCode );
			if ($level < 5) {
				GeoCodeUtil::calGeoCode ( $node, $dGeoCode, $level + 1 );
			}
		}
	}
	public static function calSqlScript($node, &$dGeoCode, $level, $outHandle) {
		$dGeoCode = $dGeoCode + GeoCodeUtil::$dOffside [$level];
		// $geoCode = decbin($dGeoCode);
		$geoCode = $dGeoCode;
		$geoName = $node->getAttribute ( "Desc" );
		$sqlLine = "insert into " . GeoCodeUtil::$tableName . " (geoCode, displayName) VALUES( " . $geoCode . ", '" . $geoName . "'); \n";
		fwrite ( $outHandle, $sqlLine );
		$subList = $node->getChildren ();
		if (! $subList) {
			return;
		}
		foreach ( $subList as $subNode ) {
			GeoCodeUtil::calSqlScript ( $subNode, $dGeoCode, $level + 1, $outHandle );
		}
	}
}

?>
